/*
 * Famon (http://code.google.com/p/famon/)
 * Copyright (c) 2010 Carl Crous.
 *
 * This file is part of Famon.
 *
 * Famon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Famon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Famon.  If not, see <http://www.gnu.org/licenses/>.
 */

package za.org.foobar.famon.collector;

import com.google.appengine.api.users.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import za.org.foobar.famon.model.*;
import za.org.foobar.famon.model.StackTraceElement;
import za.org.foobar.famon.util.DB;
import za.org.foobar.famon.webapp.UserLogic;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;



/**
 *
 */
public class InsertDataServlet extends HttpServlet {
    private final Log log = LogFactory.getLog(getClass());
    private static PrintWriter wr;
    private SourceLocationStore sourceLocationStore = new SourceLocationStore();
    private Random rand;
    private String[] messages = {
            "Invalid argument exception", "File not found", "Null pointer exception", "User parameter exception",
            "The sky is blue", "Foo Bar Baz"
    };
    private String[] users = {
            "Joe.Blogs", "Foo.Bar", "Piet.Smith", "Han.Solo", "Sponge.Bob", "John.Smith"
    };
    private String[] failureTypes = {
            "com.sun.foo.InvalidArgumentException", "java.io.FileNotFound", "java.lang.NullPointerException",
            "java.io.IOException", "foo.bar.baz.FooBarBaz", "sky.blue.Optimistic" 
    };
    public static Map<Long, SourceLocationData[]> sourceLocationData = new HashMap<Long, SourceLocationData[]>();

    private String[] logMessages = {
            "There are 993939 items in the list",
            "What is going on?!",
            "This should never happen",
            "I'm 99% sure this won't crash",
            "What mutant ninja host controller has more than one endpoint?!"
    };
    private String[] logLevels = {
            "DEBUG", "INFO", "WARN", "FAIL"
    };
    private String[] loggerNames = {
            "com.sun.SomeClass", "za.co.google.FooBar", "java.io.Read", "foo.bar.Baz"
    };
    private String[] fileNames = {
            "com/sun/package/AbcClass.java", "java/file/XyzClass.java", "com/google/api/Search.java",
            "za/co/robust/package/ClassName"
    };
    private String[] methods = {
            "abcMethod", "xyzMethod", "getName", "getId", "setUserName", "setServerContext", "initialise",
            "killMe", "whoAmI", "rmDashRfForwardSlash", "sudoKill"
    };
    private String[] classes = {
            "javax.servlet.http.AbcClass", "com.sun.mysql.XyzClass", "aa.bb.cc.Alphabet", "some.package.SomeClass",
            "this.is.not.Cool", "eye.can.not.think.OfAnyMoreClasses", "some.Test"
    };

    private String[] emails = {
            "test@example.com", "a@a.a",
            "joe.blogs@gmail.com", "foo.bar@gmail.com", "chuck@norris", "jim.smith@gmail.com", "admin.dev@gmail.com",
            "piet.schoeman@gmail.com"
    };

    private String[] hostnames = {
            "pc1", "pc2", "pc3", "dev.google.com", "dev.yahoo.com", "dev.microsoft.com" 
    };

    public void println(String str) {
        log.info(str);
        wr.println(str);
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            wr = new PrintWriter(response.getOutputStream());
            UserLogic.get().setSystemUser(true);
            rand = new Random(1234);
            wr.println();
            wr.println();
            wr.println("<html><body><pre>");
            println("Got request from " + request.getRemoteHost() + "(" + request.getRemoteAddr() + "):" +
                    request.getRemotePort());                                                  

            Application famon = addApplication("famon");
            famon.setPrivate(true);
            Application foo = addApplication("foo");
            Application bar = addApplication("bar");
            Application baz = addApplication("baz");

            ApplicationInstance famonDefault = addApplicationInstance(famon, "Default");
            ApplicationInstance famonBeta = addApplicationInstance(famon, "beta");
            ApplicationInstance famonUat = addApplicationInstance(famon, "UAT");
            ApplicationInstance famonProduction = addApplicationInstance(famon, "Prod");
            ApplicationInstance fooDefault = addApplicationInstance(foo, "default");
            ApplicationInstance barDev = addApplicationInstance(bar, "dev");
            ApplicationInstance bazDefault = addApplicationInstance(baz, "default");

            ApplicationVersion famon0_1 = addApplicationVersion(famon, "0.1");
            ApplicationVersion famon1_0 = addApplicationVersion(famon, "1.0");
            ApplicationVersion foo1_0 = addApplicationVersion(foo, "1.0");
            ApplicationVersion bar1_0 = addApplicationVersion(bar, "1.0");
            ApplicationVersion baz1_0 = addApplicationVersion(baz, "1.0");

            setupSourceLocations(famon0_1);
            setupSourceLocations(famon1_0);
            setupSourceLocations(foo1_0);
            setupSourceLocations(bar1_0);
            setupSourceLocations(baz1_0);

            // Do NOT call addFailures for the same application group twice!
            addFailures(famon, famonDefault, famon0_1, 50);
            addFailures(famon, famonDefault, famon1_0, 2);
            addFailures(famon, famonBeta, famon0_1, 5);
            addFailures(famon, famonProduction, famon1_0, 2);

            addFailures(foo, fooDefault, foo1_0, 10);
            addFailures(bar, barDev, bar1_0, 12);
            addFailures(baz, bazDefault, baz1_0, 8);

            // Users
            User userArray[] = new User[emails.length];
            UserData userData[] = new UserData[emails.length];
            for (int i = 0; i < emails.length; i++) {
                String email = emails[i];
                if (i != 1) {
                    String domain = email.substring(email.lastIndexOf('@') + 1);
                    userArray[i] = new User(email, domain);
                    userData[i] = new UserData(userArray[i]);
                } else {
                    userData[i] = new UserData(email);
                }
            }
            DB.persistAll(userData);

            ApplicationUser appUsers[] = new ApplicationUser[4];
            for (int i = 0; i < 4; i++) {
                appUsers[i] = new ApplicationUser(famon, userData[i].getId(), i == 0 ? RoleEnum.admin() : RoleEnum.user());
            }
            DB.persistAll(appUsers);

        } catch (Exception e) {
            println("Exception: " + e.getMessage());
            e.printStackTrace(wr);
            log.info("", e);
        } finally {
            wr.println("</pre></body></html>");
            wr.close();
            response.getOutputStream().close();
            DB.closePM();
            UserLogic.get().setSystemUser(false);
        }
    }

    private Application addApplication(String name) {
        Application app = new Application(name);
        DB.persist(app);
        println("Added Application: \"" + name + "\": " + app);
        return app;
    }

    private ApplicationInstance addApplicationInstance(Application app, String name) {
        ApplicationInstance instance = new ApplicationInstance(name);
        instance.setApplication(app);
        DB.persist(instance);
        println("Added ApplicationInstance: \"" + name + "\": " + instance);
        return instance;
    }

    private ApplicationVersion addApplicationVersion(Application app, String name) {
        ApplicationVersion version = new ApplicationVersion(name);
        version.setApplicationId(app.getId());
        DB.persist(version);
        println("Added ApplicationVersion: \"" + name + "\": " + version);
        return version;
    }

    private ApplicationGroup addApplicationGroup(Application app, ApplicationInstance instance, ApplicationVersion version) {
        ApplicationGroup group = new ApplicationGroup(app.getId(), instance.getId(), version.getId());
        DB.persist(group);
        return group;
    }

    private void setupSourceLocations(ArrayList<ApplicationVersion> versionList) {
        for (ApplicationVersion version : versionList) {
            setupSourceLocations(version);
        }
    }

    private void setupSourceLocations(ApplicationVersion version) {
        SourceLocationData data[] = new SourceLocationData[rand.nextInt(100)];
        for (int i = 0; i < data.length; i++) {
            SourceLocationData d = new SourceLocationData(version, getFileName(), getLineNumber(), getClassName(), getMethodName());
            sourceLocationStore.addSourceLocation(d.version, d.fileName, d.lineNumber, d.className, d.methodName);
            data[i] = d;
        }
        sourceLocationData.put(version.getId().getId(), data);
        sourceLocationStore.createEntries();
    }

    private String getMethodName() {
        return methods[rand.nextInt(methods.length)];
    }

    private String getClassName() {
        return classes[rand.nextInt(classes.length)];
    }

    private int getLineNumber() {
        return rand.nextInt(500) + 20;
    }

    private String getFileName() {
        return fileNames[rand.nextInt(fileNames.length)];
    }

    private ArrayList<Failure> addFailures(Application app, ApplicationInstance instance, ApplicationVersion version, int size) {
        ApplicationGroup group = addApplicationGroup(app, instance, version);
        ArrayList<Failure> list = new ArrayList<Failure>(size);
        for (int i = 0; i < size; i++) {
            list.add(addFailure(group, version));
        }
        println("Added " + size + " Failures for : " + app + ", " + instance + ", " + version);
        return list;
    }
    private Failure addFailure(ApplicationGroup group, ApplicationVersion version) {
        String user = getUser();
        String failureType = getFailureType();
        String message = getMessage();
        Date date = getDate();
        Failure f = new Failure(group.getId(), user, failureType, message, date);
        f.setSourceAddress(getAddress());
        f.setSourceHostname(getHostname());
        f.setOriginalSourceAddress(getAddress());
        f.setOriginalSourceHostname(getHostname());

        f.setThreadList(getThreadList(version,
                getIntSizes(rand.nextInt(4)+1, 10, 200),
                getIntSizes(rand.nextInt(4)+1, 2, 20)));
        DB.persist(f);
        return f;
    }

    private String getHostname() {
        return hostnames[rand.nextInt(hostnames.length)];
    }

    private String getAddress() {
        return rand.nextInt(256) + "." + rand.nextInt(256) + "." + rand.nextInt(256) + "." + rand.nextInt(256);
    }

    private List<Integer> getIntSizes(int size, int subMin, int subMax) {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < size; i++) {
            list.add(rand.nextInt(subMax)+subMin);
        }
        return list;
    }

    private String getMessage() {
        return messages[rand.nextInt(messages.length)];
    }

    private String getFailureType() {
        return failureTypes[rand.nextInt(failureTypes.length)];
    }

    private String getUser() {
        return users[rand.nextInt(users.length)];
    }

    private Date getDate() {
        // Ms since 1 jan 1970
        // 2010 = 40 years = 40*365*24*60*60*1000 = 1261440000000
        // 1 year = 365*24*60*60*1000 = 31536000000
        return new Date(Calendar.getInstance().getTimeInMillis() - Math.abs(rand.nextInt()));
    }

    private List<ThreadInformation> getThreadList(ApplicationVersion version, List<Integer> stackTraceSizes, List<Integer> loggingEventSizes) {
        List<ThreadInformation> list = new ArrayList<ThreadInformation>();
        for (int i = 0; i < Math.min(stackTraceSizes.size(), loggingEventSizes.size()); i++) {
            list.add(getThread(version, i, stackTraceSizes.get(i), loggingEventSizes.get(i)));
        }
        return list;
    }

    private ThreadInformation getThread(ApplicationVersion version, int index, int stackTraceSize, int loggingEventSize) {
        ThreadInformation i = new ThreadInformation(getThreadName(index));
        i.setStackTrace(getStackTrace(version, stackTraceSize));
        i.setLoggingEventList(getLoggingEventList(version, loggingEventSize));
        return i;
    }

    private List<StackTraceElement> getStackTrace(ApplicationVersion version, int stackTraceSize) {
        List<StackTraceElement> list = new ArrayList<StackTraceElement>();
        for (int i = 0; i < stackTraceSize; i++) {
            list.add(getStackTraceElement(version, i));
        }
        return list;
    }

    private StackTraceElement getStackTraceElement(ApplicationVersion version, int index) {
        return new StackTraceElement(index, getSourceLocation(version, index));
    }
    
    private List<LoggingEvent> getLoggingEventList(ApplicationVersion version, int size) {
        List<LoggingEvent> list = new ArrayList<LoggingEvent>();
        for (int i = 0; i < size; i++) {
            list.add(getLoggingEvent(version, i));
        }
        return list;
    }

    private LoggingEvent getLoggingEvent(ApplicationVersion version, int index) {
        String loggerName = getLoggerName();
        String level = getLevel();
        String message = getLogMessage();
        return new LoggingEvent(index, loggerName, level, message, getSourceLocation(version, index));
    }

    private String getLogMessage() {
        return logMessages[rand.nextInt(logMessages.length)];
    }

    private String getLevel() {
        return logLevels[rand.nextInt(logLevels.length)];
    }

    private String getLoggerName() {
        return loggerNames[rand.nextInt(loggerNames.length)];
    }

    private SourceLocation getSourceLocation(ApplicationVersion version, int index) {
        SourceLocationData d = getSourceLocationData(version, index);
//        println("For version " + version + ", got " + d);
        SourceLocation sl = sourceLocationStore.getSourceLocation(version, d.fileName, d.lineNumber, d.className, d.methodName);
        if (sl == null) {
            println("SourceLocation is NULL for " + version + ", " + d.fileName + ", " + d.lineNumber + ", " + d.className + ", " + d.methodName);
        }
        return sl;
    }

    private SourceLocationData getSourceLocationData(ApplicationVersion version, int index) {
        SourceLocationData data[] = sourceLocationData.get(version.getId().getId());
        return data[index % data.length];
    }

    private static class SourceLocationData {
        ApplicationVersion version;
        String fileName;
        int lineNumber;
        String className;
        String methodName;

        private SourceLocationData(ApplicationVersion version, String fileName, int lineNumber, String className, String methodName) {
            this.version = version;
            this.fileName = fileName;
            this.lineNumber = lineNumber;
            this.className = className;
            this.methodName = methodName;
        }
    }


    private String getThreadName(int index) {
        return "thread" + index;
    }

    private void redirect(HttpServletResponse response) {
        response.setContentType("text/plain");
        response.setStatus(response.SC_MOVED_TEMPORARILY);
        response.setHeader("Location", "/");
    }
}
